#ifndef UFO_ATTACK_TEXTURE_INCLUDED
#define UFO_ATTACK_TEXTURE_INCLUDED

#include "../grinliz/gldebug.h"
#include "../grinliz/gltypes.h"
#include "../grinliz/glstringutil.h"
#include "../shared/gamedbreader.h"
#include "../engine/ufoutil.h"
#include "../shared/glmap.h"

class Surface;
class Texture;
struct GPUMem;

class ITextureCreator
{
public:
	virtual void CreateTexture( Texture* t ) = 0;
};

class Texture
{
	friend class TextureManager;
public:
	enum { MAX_TEXTURE_NAME = 24 };

	// WARNING: duplicated in Surface
	enum  {			// channels	bytes
		RGBA16,		// 4444		2
		RGB16,		// 565		2
		ALPHA,		// 8		1
	};

	enum Param {
		PARAM_NONE		= 0,
		//PARAM_NEAREST	= 0x01,
		PARAM_LINEAR	= 0x02,
	};

	Texture()					{ m_creator = 0; } 

	const char* Name() const	{ return m_name.c_str(); }
	bool Alpha() const			{ return (m_format != RGB16); }; 
	float AspectRatio() const	{ return (float)m_w / (float)m_h; }

	int Format() const			{ return m_format; }
	int Flags() const			{ return m_flags; }

	void Upload( const void* mem, int size );
	void Upload( const Surface& surface );
	bool Empty() const			{ return m_creator == 0 && m_item == 0 && m_gpuMem == 0 && m_name.empty(); }

	U32 GLID();

private:
	int BytesInImage() const	{ return m_w*m_h*BytesPerPixel(); }
	int BytesPerPixel() const	{ return (m_format == ALPHA) ? 1 : 2; }
	void Set( const char* name, int w, int h, int format, Param flags );

	grinliz::CStr< MAX_TEXTURE_NAME > m_name;
	int	  m_w, m_h, m_format;
	Param m_flags;

	ITextureCreator* m_creator;			// if generated by the host
	const gamedb::Item* m_item;			// if attached to the database

	// This is the connection to the memory allocation. (Could be a pointer.)
	// Can be set to 0 at any time, in which case a new request for memory
	// is sent to the TextureManager
	const GPUMem* m_gpuMem;
};


struct GPUMem
{
	U32 Memory() const { return w*h*((format==Texture::ALPHA)?1:2); }

	int w, h, format, flags;	// basic texture properties
	const gamedb::Item*	item;	// item cached at this memory location
	bool inUse;					// this has a cached glID referencing it.
	U32 glID;					// memory reference to the GPU
};


class TextureManager
{
public:
	static TextureManager* Instance()	{ GLASSERT( instance ); return instance; }

	void DeviceLoss();
	void Reload();

	bool IsTexture( const char* name );
	Texture* GetTexture( const char* name, bool reload=false );
	Texture* CreateTexture( const char* name, int w, int h, int format, 
							Texture::Param parems = Texture::PARAM_NONE, 
							ITextureCreator* create = 0 );
	void DeleteTexture( Texture* );

	void ContextShift();

	unsigned NumTextures() const			{ return textureArr.Size(); }
	unsigned NumGPUResources() const		{ return gpuMemArr.Size(); }
	U32 CalcTextureMem() const;
	U32 CalcGPUMem() const;
	int CacheHit() const					{ return cacheHit; }
	int CacheReuse() const					{ return cacheReuse; }
	int CacheMiss() const					{ return cacheMiss; }

	// called by the texture.
	const GPUMem* AllocGPUMemory( int w, int h, int format, int flags, const gamedb::Item* item, bool* inCache );
	// called by the texture
	void CalcOpenGL( int format, int* glFormat, int* glType );

	static void Create( const gamedb::Reader* );
	static void Destroy();

	void* WorkingMemory( int w, int h );

private:
	TextureManager( const gamedb::Reader* );
	~TextureManager();

	U32 CreateGLTexture( int w, int h, int format, int flags );

	enum {
		MAX_TEXTURES = 100		// increase as needed
	};

	static TextureManager* instance;
	const gamedb::Reader* database;

	int cacheMiss;
	int cacheHit;
	int cacheReuse;
	int emptySpace;

	CArray< Texture, MAX_TEXTURES > textureArr;		// textures
	CArray< GPUMem, MAX_TEXTURES > gpuMemArr;		// references into the GPU mem
	CStringMap<	Texture* > map;
	CMap< const gamedb::Item*, GPUMem* > gpuMap;
};

#endif // UFO_ATTACK_TEXTURE_INCLUDED
